package com.alcatel_lucent.osp.service;

import com.alcatel_lucent.osp.BusinessException;
import com.alcatel_lucent.osp.model.User;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.*;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Service;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import java.util.Arrays;
import java.util.List;

import static org.springframework.ldap.core.DistinguishedName.EMPTY_PATH;


@Service("ldapService")
public class LDAPServiceImpl implements LDAPService {
    private static Logger log = LoggerFactory.getLogger(LDAPServiceImpl.class);

    @Value("${ldap.url}")
    private String ldapUrl;

    @Autowired
    private LdapTemplate ldapTemplate;

    private static final int TRY_COUNT = 3;


    public boolean login(String username, String password) {
        String filter = buildUserFilter(
                new OrFilter().
                        or(new EqualsFilter("uid", username)).
                        or(new EqualsFilter("cn", username)).
                        or(new EqualsFilter("cil", username))
        ).encode();

        int tryCount = TRY_COUNT;

        BusinessException businessException = null;
        while (tryCount-- > 0) {
            try {
                log.info("login search filer={}",filter);
                return ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter, password);
            } catch (org.springframework.ldap.ServiceUnavailableException e) {
                log.error("Ldap exception: {}", e);
                businessException = new BusinessException(BusinessException.LDAP_CONNECTION_ERROR, e.getMessage());
            }
        }
        throw businessException;
    }

    private static class UserAttributesMapper implements AttributesMapper {
        private static UserAttributesMapper me = new UserAttributesMapper();

        public static UserAttributesMapper getInstance() {
            return me;
        }

        @Override
        public Object mapFromAttributes(Attributes attributes) throws NamingException {
            Attribute csl = attributes.get("uid");
            Attribute email = attributes.get("mail");
            String cil = (String) attributes.get("cil").get();
            return new User(csl.get().toString(), email.get().toString(), cil);
        }
    }
    public AttributesMapper getUserAttributesMapper(){
        return UserAttributesMapper.getInstance();
    }


    public List<User> findUsers(String filter) {
        log.info("String filter={}", filter);
        List<User> users = ldapTemplate.search(LdapUtils.emptyLdapName(), filter, getUserAttributesMapper());
        return users;
    }

    public List<User> findUserByCILParts(String cil) {

        List<String> cnTokens = Arrays.asList(cil.split("\\s"));
        if (cnTokens.size() < 2) {
            //cil supposed to be csl
            AndFilter andFilter = buildUserFilter(new EqualsFilter("uid", cil));
            return findUsers(andFilter.encode());
        }

        AndFilter andFilter = buildUserFilter("");
        for (String cnToken : cnTokens) {
            andFilter.and(new WhitespaceWildcardsFilter("cil", cnToken));
        }

        String strFilter = andFilter.encode();
        return findUsers(strFilter);
    }

    private AndFilter buildUserFilter(Filter filter) {
        AndFilter andFilter = new AndFilter().and(new EqualsFilter("objectclass", "person")).
                and(new PresentFilter("mail")).
                and(new PresentFilter("uid")).
                and(new PresentFilter("cil"));
        if (null != filter) andFilter.and(filter);
        return andFilter;
    }

    private AndFilter buildUserFilter(String filter) {
        Filter filter1 = null;
        if (StringUtils.isNotEmpty(filter)) filter1 = new HardcodedFilter(filter);
        return buildUserFilter(filter1);
    }

    public User findUserByCSLOrCIL(String cslOrCil) {
        String filter = buildUserFilter(
                new OrFilter().
                        or(new EqualsFilter("uid", cslOrCil)).
                        or(new EqualsFilter("cn", cslOrCil)).
                        or(new EqualsFilter("cil", cslOrCil))
        ).encode();
        List<User> users = findUsers(filter);
        if (users.isEmpty()) return null;
        return users.get(0);
    }

}
